// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Tests for the QueryTracker.

#include "gpu/command_buffer/client/query_tracker.h"

#include <GLES2/gl2ext.h>
#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <vector>

#include "gpu/command_buffer/client/client_test_helper.h"
#include "gpu/command_buffer/client/gles2_cmd_helper.h"
#include "gpu/command_buffer/client/mapped_memory.h"
#include "gpu/command_buffer/common/command_buffer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace gpu {
namespace gles2 {

    class QuerySyncManagerTest : public testing::Test {
    protected:
        static const int32_t kNumCommandEntries = 400;
        static const int32_t kCommandBufferSizeBytes = kNumCommandEntries * sizeof(CommandBufferEntry);

        void SetUp() override
        {
            command_buffer_.reset(new MockClientCommandBuffer());
            helper_.reset(new GLES2CmdHelper(command_buffer_.get()));
            helper_->Initialize(kCommandBufferSizeBytes);
            mapped_memory_.reset(
                new MappedMemoryManager(helper_.get(), MappedMemoryManager::kNoLimit));
            sync_manager_.reset(new QuerySyncManager(mapped_memory_.get()));
        }

        void TearDown() override
        {
            sync_manager_.reset();
            mapped_memory_.reset();
            helper_.reset();
            command_buffer_.reset();
        }

        std::unique_ptr<CommandBuffer> command_buffer_;
        std::unique_ptr<GLES2CmdHelper> helper_;
        std::unique_ptr<MappedMemoryManager> mapped_memory_;
        std::unique_ptr<QuerySyncManager> sync_manager_;
    };

    TEST_F(QuerySyncManagerTest, Basic)
    {
        QuerySyncManager::QueryInfo infos[4];
        memset(&infos, 0xBD, sizeof(infos));

        for (size_t ii = 0; ii < arraysize(infos); ++ii) {
            EXPECT_TRUE(sync_manager_->Alloc(&infos[ii]));
            EXPECT_NE(0, infos[ii].shm_id);
            ASSERT_TRUE(infos[ii].sync != NULL);
            EXPECT_EQ(0, infos[ii].sync->process_count);
            EXPECT_EQ(0u, infos[ii].sync->result);
        }

        for (size_t ii = 0; ii < arraysize(infos); ++ii) {
            sync_manager_->Free(infos[ii]);
        }
    }

    TEST_F(QuerySyncManagerTest, DontFree)
    {
        QuerySyncManager::QueryInfo infos[4];
        memset(&infos, 0xBD, sizeof(infos));

        for (size_t ii = 0; ii < arraysize(infos); ++ii) {
            EXPECT_TRUE(sync_manager_->Alloc(&infos[ii]));
        }
    }

    class QueryTrackerTest : public testing::Test {
    protected:
        static const int32_t kNumCommandEntries = 400;
        static const int32_t kCommandBufferSizeBytes = kNumCommandEntries * sizeof(CommandBufferEntry);

        void SetUp() override
        {
            command_buffer_.reset(new MockClientCommandBuffer());
            helper_.reset(new GLES2CmdHelper(command_buffer_.get()));
            helper_->Initialize(kCommandBufferSizeBytes);
            mapped_memory_.reset(
                new MappedMemoryManager(helper_.get(), MappedMemoryManager::kNoLimit));
            query_tracker_.reset(new QueryTracker(mapped_memory_.get()));
        }

        void TearDown() override
        {
            query_tracker_.reset();
            mapped_memory_.reset();
            helper_.reset();
            command_buffer_.reset();
        }

        QuerySync* GetSync(QueryTracker::Query* query)
        {
            return query->info_.sync;
        }

        QuerySyncManager::Bucket* GetBucket(QueryTracker::Query* query)
        {
            return query->info_.bucket;
        }

        uint32_t GetBucketUsedCount(QuerySyncManager::Bucket* bucket)
        {
            return bucket->in_use_queries.count();
        }

        uint32_t GetFlushGeneration() { return helper_->flush_generation(); }

        std::unique_ptr<CommandBuffer> command_buffer_;
        std::unique_ptr<GLES2CmdHelper> helper_;
        std::unique_ptr<MappedMemoryManager> mapped_memory_;
        std::unique_ptr<QueryTracker> query_tracker_;
    };

    TEST_F(QueryTrackerTest, Basic)
    {
        const GLuint kId1 = 123;
        const GLuint kId2 = 124;

        // Check we can create a Query.
        QueryTracker::Query* query = query_tracker_->CreateQuery(
            kId1, GL_ANY_SAMPLES_PASSED_EXT);
        ASSERT_TRUE(query != NULL);
        // Check we can get the same Query.
        EXPECT_EQ(query, query_tracker_->GetQuery(kId1));
        // Check we get nothing for a non-existent query.
        EXPECT_TRUE(query_tracker_->GetQuery(kId2) == NULL);
        // Check we can delete the query.
        query_tracker_->RemoveQuery(kId1);
        // Check we get nothing for a non-existent query.
        EXPECT_TRUE(query_tracker_->GetQuery(kId1) == NULL);
    }

    TEST_F(QueryTrackerTest, Query)
    {
        const GLuint kId1 = 123;
        const int32_t kToken = 46;
        const uint32_t kResult = 456;

        // Create a Query.
        QueryTracker::Query* query = query_tracker_->CreateQuery(
            kId1, GL_ANY_SAMPLES_PASSED_EXT);
        ASSERT_TRUE(query != NULL);
        EXPECT_TRUE(query->NeverUsed());
        EXPECT_FALSE(query->Pending());
        EXPECT_EQ(0, query->token());
        EXPECT_EQ(0, query->submit_count());

        // Check MarkAsActive.
        query->MarkAsActive();
        EXPECT_FALSE(query->NeverUsed());
        EXPECT_FALSE(query->Pending());
        EXPECT_EQ(0, query->token());
        EXPECT_EQ(1, query->submit_count());

        // Check MarkAsPending.
        query->MarkAsPending(kToken);
        EXPECT_FALSE(query->NeverUsed());
        EXPECT_TRUE(query->Pending());
        EXPECT_EQ(kToken, query->token());
        EXPECT_EQ(1, query->submit_count());

        // Flush only once if no more flushes happened between a call to
        // EndQuery command and CheckResultsAvailable
        // Advance put_ so flush calls in CheckResultsAvailable go through
        // and updates flush_generation count
        helper_->Noop(1);

        // Store FlushGeneration count after EndQuery is called
        uint32_t gen1 = GetFlushGeneration();

        // Check CheckResultsAvailable.
        EXPECT_FALSE(query->CheckResultsAvailable(helper_.get()));
        EXPECT_FALSE(query->NeverUsed());
        EXPECT_TRUE(query->Pending());

        uint32_t gen2 = GetFlushGeneration();
        EXPECT_NE(gen1, gen2);

        // Repeated calls to CheckResultsAvailable should not flush unnecessarily
        EXPECT_FALSE(query->CheckResultsAvailable(helper_.get()));
        gen1 = GetFlushGeneration();
        EXPECT_EQ(gen1, gen2);
        EXPECT_FALSE(query->CheckResultsAvailable(helper_.get()));
        gen1 = GetFlushGeneration();
        EXPECT_EQ(gen1, gen2);

        // Simulate GPU process marking it as available.
        QuerySync* sync = GetSync(query);
        sync->process_count = query->submit_count();
        sync->result = kResult;

        // Check CheckResultsAvailable.
        EXPECT_TRUE(query->CheckResultsAvailable(helper_.get()));
        EXPECT_EQ(kResult, query->GetResult());
        EXPECT_FALSE(query->NeverUsed());
        EXPECT_FALSE(query->Pending());
    }

    TEST_F(QueryTrackerTest, Remove)
    {
        const GLuint kId1 = 123;
        const int32_t kToken = 46;
        const uint32_t kResult = 456;

        // Create a Query.
        QueryTracker::Query* query = query_tracker_->CreateQuery(
            kId1, GL_ANY_SAMPLES_PASSED_EXT);
        ASSERT_TRUE(query != NULL);

        QuerySyncManager::Bucket* bucket = GetBucket(query);
        EXPECT_EQ(1u, GetBucketUsedCount(bucket));

        query->MarkAsActive();
        query->MarkAsPending(kToken);

        query_tracker_->RemoveQuery(kId1);
        // Check we get nothing for a non-existent query.
        EXPECT_TRUE(query_tracker_->GetQuery(kId1) == NULL);

        // Check that memory was not freed.
        EXPECT_EQ(1u, GetBucketUsedCount(bucket));

        // Simulate GPU process marking it as available.
        QuerySync* sync = GetSync(query);
        sync->process_count = query->submit_count();
        sync->result = kResult;

        // Check FreeCompletedQueries.
        query_tracker_->FreeCompletedQueries();
        EXPECT_EQ(0u, GetBucketUsedCount(bucket));
    }

    TEST_F(QueryTrackerTest, ManyQueries)
    {
        const GLuint kId1 = 123;
        const int32_t kToken = 46;
        const uint32_t kResult = 456;

        const size_t kTestSize = 4000;
        static_assert(kTestSize > QuerySyncManager::kSyncsPerBucket,
            "We want to use more than one bucket");
        // Create lots of queries.
        std::vector<QueryTracker::Query*> queries;
        for (size_t i = 0; i < kTestSize; i++) {
            QueryTracker::Query* query = query_tracker_->CreateQuery(kId1 + i, GL_ANY_SAMPLES_PASSED_EXT);
            ASSERT_TRUE(query != NULL);
            queries.push_back(query);
            QuerySyncManager::Bucket* bucket = GetBucket(query);
            EXPECT_LE(1u, GetBucketUsedCount(bucket));
        }

        QuerySyncManager::Bucket* query_0_bucket = GetBucket(queries[0]);
        uint32_t expected_use_count = QuerySyncManager::kSyncsPerBucket;
        EXPECT_EQ(expected_use_count, GetBucketUsedCount(query_0_bucket));

        while (!queries.empty()) {
            QueryTracker::Query* query = queries.back();
            queries.pop_back();
            GLuint query_id = kId1 + queries.size();
            EXPECT_EQ(query_id, query->id());
            query->MarkAsActive();
            query->MarkAsPending(kToken);

            QuerySyncManager::Bucket* bucket = GetBucket(query);
            uint32_t use_count_before_remove = GetBucketUsedCount(bucket);
            query_tracker_->FreeCompletedQueries();
            EXPECT_EQ(use_count_before_remove, GetBucketUsedCount(bucket));
            query_tracker_->RemoveQuery(query_id);
            // Check we get nothing for a non-existent query.
            EXPECT_TRUE(query_tracker_->GetQuery(query_id) == NULL);

            // Check that memory was not freed since it was not completed.
            EXPECT_EQ(use_count_before_remove, GetBucketUsedCount(bucket));

            // Simulate GPU process marking it as available.
            QuerySync* sync = GetSync(query);
            sync->process_count = query->submit_count();
            sync->result = kResult;

            // Check FreeCompletedQueries.
            query_tracker_->FreeCompletedQueries();
            EXPECT_EQ(use_count_before_remove - 1, GetBucketUsedCount(bucket));
        }
    }

} // namespace gles2
} // namespace gpu
